//
// Created by 郝羽 on 2017/12/6.
//

#ifndef RTC_XSWITCH2_INTERARRIVAL_BITRATE_ESTIMATOR_HPP
#define RTC_XSWITCH2_INTERARRIVAL_BITRATE_ESTIMATOR_HPP

#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <cstdint>
#include <deque>
#include <map>
#include <stdio.h>

#include "bwe/bwe.hpp"
#include "bwe/aimd_rate_control.hpp"
#include "bwe/bitrate.hpp"
#include "bwe/inter_arrival.hpp"
#include "bwe/overuse_detector.hpp"
#include "bwe/overuse_estimator.hpp"
#include "bwe/packet_hist.hpp"
#include "rtp/rtcp/transport_feedback.hpp"

namespace bwe {
    class SourceEstimator {
    public:
        SourceEstimator(int64_t now_ms, uint32_t ssrc)
                : ssrc(ssrc),
                  last_packet_time_ms(now_ms),
                  inter_arrival(450, 1.0/90.0), // for rtp timestamp
                  overuse_estimator(ssrc)
        {};

    public:
        uint32_t            ssrc;
        int64_t             last_packet_time_ms;
        InterArrival        inter_arrival;
        OveruseEstimator    overuse_estimator;
        OveruseDetector     overuse_detector;
    };

    class InterArrivalEstimator {
        const char* debug_id_ = nullptr;
        FILE* debug_in_ = nullptr;
        FILE* debug_out_ = nullptr;
        bool debug_out_first_ = true;
        uint32_t debug_ssrc_;
    public:
        InterArrivalEstimator(bool debug, const char* debug_id, uint32_t debug_ssrc)
                : debug_id_(debug_id), debug_ssrc_(debug_ssrc) {
            if (debug) {
                char fn[1024] = {0,};
                sprintf(fn, "/tmp/%s.csv", debug_id);
                debug_in_ = fopen(fn, "w");
                if (debug_in_) {
                    fprintf(debug_in_, "Timestamp,rtp_ts,arrival,size\n");
                }

                char fn2[1024] = {0,};
                sprintf(fn2, "/tmp/%s.json", debug_id);
                debug_out_ = fopen(fn2, "w");
                if (debug_out_) {
                    fprintf(debug_out_, "[\n");
                }
            }
        };

        ~InterArrivalEstimator() {
            auto it = estimators_.begin();
            while (it != estimators_.end()) {
                SourceEstimator* e = it->second;
                delete(e);
                estimators_.erase(it++);
            }

            if (debug_out_) {
                fprintf(debug_out_, "]\n");
                fclose(debug_out_);
                debug_out_ = nullptr;
            }
            if (debug_in_) {
                fclose(debug_in_);
                debug_in_ = nullptr;
            }
        }

        // return true if overuse
        bool IncomingPacket(uint32_t ssrc, int64_t arrival, uint32_t rtp_ts, uint32_t size, int64_t now_ms) {
            // find estimator by ssrc
            auto it = estimators_.find(ssrc);
            if (it == estimators_.end()) {
                auto insert = estimators_.emplace(std::make_pair(ssrc, new SourceEstimator(arrival, ssrc)));
                it = insert.first;
            }

            SourceEstimator* estimator = it->second;
            estimator->last_packet_time_ms = arrival;

            uint32_t ts_delta;
            int64_t t_delta_ms;
            int size_delta;
            uint32_t bps;

            // check and update rate stats
            if (incoming_bitrate_.read(bps, arrival)) {
                last_valid_incoming_bitrate_ = bps;
            } else if (last_valid_incoming_bitrate_ > 0) {
                incoming_bitrate_.reset();
                last_valid_incoming_bitrate_ = 0;
            }
            incoming_bitrate_.update(size, arrival);

            Usage prev_usage = estimator->overuse_detector.Hypothesis();
            if (estimator->inter_arrival.ComputeDelta(ts_delta, t_delta_ms, size_delta, rtp_ts, arrival, size, now_ms)) {
                const double ts_delta_ms = ts_delta * ts_to_ms_;
                if (ssrc == debug_ssrc_ && debug_in_) {
                    int64_t now = get_timestamp_ms();
                    if (ssrc == 1843363638) {
                        //printf("%" PRId64 ",%u,%" PRId64 ",%u,%d,%" PRId64 ",%d,%u\n", now, rtp_ts, arrival, size, (int)ts_delta_ms, t_delta_ms, size_delta, bps);
                        fprintf(debug_in_, "%" PRId64 ",%u,%" PRId64 ",%u,%d,%" PRId64 ",%d,%u\n", now, rtp_ts, arrival, size, (int)ts_delta_ms, t_delta_ms, size_delta, bps);
                    }
                }
                auto deltas_offset = estimator->overuse_estimator.Estimate(arrival, prev_usage, ts_delta_ms, t_delta_ms, size_delta);
                estimator->overuse_detector.DetectUsage(arrival, ts_delta_ms, deltas_offset.second, deltas_offset.first);
            }

            if (estimator->overuse_detector.Hypothesis() == Usage::OverUsing) {
                uint32_t bps = 0;
                if (incoming_bitrate_.read(bps, arrival) && (prev_usage != Usage::OverUsing || control_.ReduceFurther(arrival, bps))) {
                    UpdateEstimation(arrival);
                    return true;
                }
            }
            return false;
        };

        void IncomingTransportFeedback(const TransportFeedback& fb) {
            const int64_t BaseTimeRangeMs = (1 << 24) * 64 / 1000;
            uint64_t timestamp_ms = fb.base_time;
            int64_t now_ms = get_timestamp_ms();
            if (_last_transport_fb_timestamp_ms == 0) {
                _current_transport_fb_offset_ms = now_ms;
            } else {
                int64_t delta = timestamp_ms - _last_transport_fb_timestamp_ms;
                if (std::abs(delta - BaseTimeRangeMs) < std::abs(delta)) {
                    delta -= BaseTimeRangeMs;
                } else if (std::abs(delta + BaseTimeRangeMs) < std::abs(delta)) {
                    delta += BaseTimeRangeMs;
                }
                _current_transport_fb_offset_ms += delta;
            }
            _last_transport_fb_timestamp_ms = timestamp_ms;
            int64_t d = 0;
            fb.for_each([&](uint16_t seq, int32_t delta) {
                d += delta;
                int64_t recv_ts = _current_transport_fb_offset_ms + d;
                PacketInfo info;
                if (send_hist_.get(info, seq)) {
                    IncomingPacket(fb.ssrc_media, recv_ts, info.rtp_ts, info.bytes, now_ms);
                } else {
                    //std::cout << "miss" << std::endl;
                }
            });
        };

        void OutgoingPacket(uint32_t seq, uint32_t rtp_ts, uint16_t bytes) {
            send_hist_.insert_packet(PacketInfo(seq, bytes, rtp_ts));
        };

        uint32_t TargetBitrate() {
            if (has_target_bitrate_) {
                return target_bitrate_;
            }
            return 300*1000;
        };

        // todo: support multiple ssrc
        void UpdateEstimation(int64_t now_ms) {
            double var_noise = 0.0;
            Usage usage = Usage::Normal;
            auto it = estimators_.begin();
            while (it != estimators_.end()) {
                SourceEstimator* estimator = it->second;
                // const int64_t last_packet_time_ms = estimator->last_packet_time_ms;
                if (!debug_out_ && estimator->last_packet_time_ms > 0 && now_ms - estimator->last_packet_time_ms > source_estimator_timeout) {
                    estimators_.erase(it++);
                    delete estimator;
                } else {
                    var_noise += estimator->overuse_estimator.VarNoise();
                    Usage u = estimator->overuse_detector.Hypothesis();
                    if (u > usage) {
                        usage = u;
                    }
                    ++it;
                }
            }

            if (estimators_.empty()) {
                return;
            }

            RateControlInput input;
            input.usage = usage;
            input.noise_var = var_noise;
            uint32_t bps = 0;
            if (incoming_bitrate_.read(bps, now_ms)) {
                input.bitrate = bps;
                input.has_bitrate = true;
            } else {
                input.has_bitrate = false;
            }

            target_bitrate_ = control_.Update(input, now_ms);
            has_target_bitrate_ = true;

            if (debug_out_)
            {
                rapidjson::StringBuffer buf;
                rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
                writer.StartObject();
                writer.Key("time");
                writer.Int64(now_ms);

                int n = 0;
                //auto it = estimators_.find(debug_ssrc_);
                for (auto it : estimators_) {
                    SourceEstimator* estimator = it.second;
                    char k1[16];
                    char k2[16];
                    char k3[16];
                    sprintf(k1, "offset-%d", n);
                    sprintf(k2, "threshold-%d", n);
                    sprintf(k3, "var_noise-%d", n);
                    writer.Key(k1);
                    writer.Double(estimator->overuse_estimator.Offset());
                    writer.Key(k2);
                    writer.Double(estimator->overuse_detector.Threshold());
                    writer.Key(k3);
                    writer.Double(estimator->overuse_estimator.VarNoise());

//                    std::cout << " ssrc: " << estimator->ssrc;
//                    std::cout << " time: " << now_ms << " offset: " << estimator->overuse_estimator.Offset();
//                    std::cout << " threshold: " << estimator->overuse_detector.Threshold() << " target: " << target_bitrate_;
//                    std::cout << " incoming: " << bps;
//                    std::cout << " var-noise: " << estimator->overuse_estimator.VarNoise();
//                    std::cout << " usage: " << (int)usage << std::endl;

                    ++n;
                }
                if (n == 0) {
                    writer.Key("offset-0");
                    writer.Double(0);
                    writer.Key("threshold-0");
                    writer.Double(0);
                    writer.Key("var_noise-0");
                    writer.Double(0);
                    writer.Key("offset-1");
                    writer.Double(0);
                    writer.Key("threshold-1");
                    writer.Double(0);
                    writer.Key("var_noise-1");
                    writer.Double(0);
                } else if (n == 1) {
                    writer.Key("offset-1");
                    writer.Double(0);
                    writer.Key("threshold-1");
                    writer.Double(0);
                    writer.Key("var_noise-1");
                    writer.Double(0);
                }

                writer.Key("incoming");
                writer.Double(bps);
                writer.Key("target");
                writer.Uint(target_bitrate_);
                writer.Key("usage");
                writer.Int((int)usage);
                writer.EndObject();

                std::string str = buf.GetString();
                //printf("json debug output: %s\n", str.c_str());
                if (debug_out_first_) {
                    fprintf(debug_out_, "%s\n", str.c_str());
                    debug_out_first_ = false;

                } else {
                    fprintf(debug_out_, ",%s\n", str.c_str());
                }
            }
        };

    private:
        const double ts_to_ms_     = 1.0 / 90;
        const int64_t source_estimator_timeout = 2000;

        Bitrate<1000> incoming_bitrate_;
        uint32_t last_valid_incoming_bitrate_ = 0;

        AimdRateControl control_;
        bool has_target_bitrate_ = false;
        uint32_t target_bitrate_ = 0;

        std::map<uint32_t, SourceEstimator*> estimators_;

        int64_t _last_transport_fb_timestamp_ms = 0;
        int64_t _current_transport_fb_offset_ms = 0;
        PacketHistory<1024> send_hist_;
    };

}

#endif //RTC_XSWITCH2_INTERARRIVAL_BITRATE_ESTIMATOR_HPP
